/*******************************************************************************************
*
*   raylib [core] example - 2D Camera platformer
*   raylib [核心] 示例 - 2D 摄像机平台游戏
*
*   Example originally created with raylib 2.5, last time updated with raylib 3.0
*   示例最初使用 raylib 2.5 创建，最后更新于 raylib 3.0
*
*   Example contributed by arvyy (@arvyy) and reviewed by Ramon Santamaria (@raysan5)
*   示例由 arvyy (@arvyy) 贡献并由 Ramon Santamaria (@raysan5) 审查
*
*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
*   BSD-like license that allows static linking with closed source software
*   示例采用未经修改的 zlib/libpng 许可证授权，这是一个 OSI 认证的类 BSD 许可证，
*   允许与闭源软件进行静态链接
*
*   Copyright (c) 2019-2024 arvyy (@arvyy)
*   版权所有 (c) 2019-2024 arvyy (@arvyy)
*
********************************************************************************************/

// 引入 raylib 核心库，提供了图形、音频、输入等功能
#include "raylib.h"
// 引入 raymath 库，提供了一系列数学运算相关的函数，如向量、矩阵操作
#include "raymath.h"

// 定义常量 G，代表重力加速度
#define G 400
// 定义常量 PLAYER_JUMP_SPD，代表玩家跳跃速度
#define PLAYER_JUMP_SPD 350.0f
// 定义常量 PLAYER_HOR_SPD，代表玩家水平移动速度
#define PLAYER_HOR_SPD 200.0f

// C++ 中的 struct (结构体) 是一种用户自定义的数据类型，可以将不同类型的数据项组合在一起
// 定义玩家结构体 Player
typedef struct Player {
    Vector2 position;   // Vector2 是 raylib 定义的二维向量类型，表示玩家的位置 (x, y)
    float speed;        // 玩家的垂直速度（用于跳跃和下落）
    bool canJump;       //布尔类型 (bool)，表示玩家是否可以跳跃 (true 或 false)
} Player;

// 定义环境物体结构体 EnvItem
typedef struct EnvItem {
    Rectangle rect;     // Rectangle 是 raylib 定义的矩形类型，表示环境物体的边界框 (x, y, width, height)
    int blocking;       // 表示该物体是否会阻挡玩家 (1 表示阻挡, 0 表示不阻挡)
    Color color;        // Color 是 raylib 定义的颜色类型，表示物体的颜色
} EnvItem;

//----------------------------------------------------------------------------------
// Module functions declaration
// 模块函数声明
//----------------------------------------------------------------------------------
// 函数声明：告诉编译器这些函数存在，它们的定义在后面
// 参数中的 * 表示指针，例如 Player *player 表示 player 是一个指向 Player 类型对象的指针
// 指针存储的是内存地址，通过指针可以间接访问和修改对象的数据

// 更新玩家状态的函数
void UpdatePlayer(Player *player, EnvItem *envItems, int envItemsLength, float delta);
// 更新摄像机中心位置的函数 (基础跟随)
void UpdateCameraCenter(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height);
// 更新摄像机中心位置并限制在地图内部的函数
void UpdateCameraCenterInsideMap(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height);
// 更新摄像机中心位置并实现平滑跟随的函数
void UpdateCameraCenterSmoothFollow(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height);
// 更新摄像机，在玩家落地时平滑调整垂直位置的函数
void UpdateCameraEvenOutOnLanding(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height);
// 更新摄像机，当玩家靠近屏幕边缘时推动摄像机的函数
void UpdateCameraPlayerBoundsPush(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height);

//------------------------------------------------------------------------------------
// Program main entry point
// 程序主入口点
//------------------------------------------------------------------------------------
int main(void) // main 函数是 C/C++ 程序的入口点
{
    // Initialization
    // 初始化
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;    // 定义常量 screenWidth，表示屏幕宽度
    const int screenHeight = 450;   // 定义常量 screenHeight，表示屏幕高度

    // InitWindow 是 raylib 的函数，用于初始化窗口
    // 参数：窗口宽度、窗口高度、窗口标题
    InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera");

    // 初始化玩家对象
    Player player = { 0 }; // { 0 } 是一种将结构体成员初始化为零值或默认值的方式
    player.position = (Vector2){ 400, 280 }; // 设置玩家初始位置
    player.speed = 0;                         // 设置玩家初始垂直速度
    player.canJump = false;                   // 设置玩家初始不可跳跃

    // 初始化环境物体数组
    // EnvItem envItems[] = { ... }; 这是 C++ 中初始化数组的方式
    // 每个 {} 代表一个 EnvItem 对象
    EnvItem envItems[] = {
        {{ 0, 0, 1000, 400 }, 0, LIGHTGRAY },    // 第一个物体：背景区域，不阻挡，浅灰色
        {{ 0, 400, 1000, 200 }, 1, GRAY },       // 第二个物体：地面，阻挡，灰色
        {{ 300, 200, 400, 10 }, 1, GRAY },       // 第三个物体：平台1，阻挡，灰色
        {{ 250, 300, 100, 10 }, 1, GRAY },       // 第四个物体：平台2，阻挡，灰色
        {{ 650, 300, 100, 10 }, 1, GRAY }        // 第五个物体：平台3，阻挡，灰色
    };

    // 计算环境物体数组的长度
    // sizeof(envItems) 是整个数组占用的字节数
    // sizeof(envItems[0]) 是数组中第一个元素占用的字节数
    // 两者相除得到数组元素的个数
    int envItemsLength = sizeof(envItems)/sizeof(envItems[0]);

    // 初始化2D摄像机对象
    Camera2D camera = { 0 }; // Camera2D 是 raylib 定义的2D摄像机结构体
    camera.target = player.position; // 摄像机初始目标对准玩家位置
    // camera.offset 是摄像机的偏移量，通常设置为屏幕中心，使得目标物体显示在屏幕中心
    camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f };
    camera.rotation = 0.0f; // 摄像机旋转角度，初始为0
    camera.zoom = 1.0f;     // 摄像机缩放级别，初始为1.0 (不缩放)

    // Store pointers to the multiple update camera functions
    // 存储指向多个摄像机更新函数的指针
    // void (*cameraUpdaters[])(...) 这是一个函数指针数组
    // 数组中的每个元素都是一个指向特定摄像机更新函数的指针
    // 这些函数都具有相同的参数列表和返回类型
    void (*cameraUpdaters[])(Camera2D*, Player*, EnvItem*, int, float, int, int) = {
        UpdateCameraCenter,
        UpdateCameraCenterInsideMap,
        UpdateCameraCenterSmoothFollow,
        UpdateCameraEvenOutOnLanding,
        UpdateCameraPlayerBoundsPush
    };

    int cameraOption = 0; // 当前选择的摄像机模式索引，初始为0
    // 计算摄像机更新函数数组的长度
    int cameraUpdatersLength = sizeof(cameraUpdaters)/sizeof(cameraUpdaters[0]);

    // 存储不同摄像机模式的描述文本
    // char *cameraDescriptions[] 这是一个字符串指针数组，每个元素指向一个描述字符串
    char *cameraDescriptions[] = {
        "Follow player center",
        "Follow player center, but clamp to map edges",
        "Follow player center; smoothed",
        "Follow player center horizontally; update player center vertically after landing",
        "Player push camera on getting too close to screen edge"
    };

    // SetTargetFPS 是 raylib 的函数，用于设置游戏的目标每秒帧数 (FPS)
    SetTargetFPS(60);
    //--------------------------------------------------------------------------------------

    // Main game loop
    // 主游戏循环
    // WindowShouldClose() 是 raylib 的函数，检测窗口是否应该关闭 (例如用户点击关闭按钮或按下 ESC 键)
    while (!WindowShouldClose())
    {
        // Update
        // 更新逻辑
        //----------------------------------------------------------------------------------
        // GetFrameTime() 是 raylib 的函数，获取上一帧所花费的时间 (单位：秒)
        // deltaTime (或 dt) 用于实现帧独立的游戏逻辑，确保游戏速度在不同帧率下保持一致
        float deltaTime = GetFrameTime();

        // 更新玩家状态，传入玩家指针、环境物体数组、数组长度和 deltaTime
        UpdatePlayer(&player, envItems, envItemsLength, deltaTime);

        // 处理摄像机缩放
        // GetMouseWheelMove() 是 raylib 的函数，获取鼠标滚轮的移动量
        camera.zoom += ((float)GetMouseWheelMove()*0.05f);

        // 限制摄像机的缩放范围
        if (camera.zoom > 3.0f) camera.zoom = 3.0f;
        else if (camera.zoom < 0.25f) camera.zoom = 0.25f;

        // IsKeyPressed(KEY_R) 是 raylib 的函数，检测 R 键是否被按下 (仅在按下的那一帧返回 true)
        if (IsKeyPressed(KEY_R))
        {
            camera.zoom = 1.0f; // 重置摄像机缩放
            player.position = (Vector2){ 400, 280 }; // 重置玩家位置
        }

        // IsKeyPressed(KEY_C) 检测 C 键是否被按下
        if (IsKeyPressed(KEY_C)) cameraOption = (cameraOption + 1)%cameraUpdatersLength;
        // 切换摄像机模式：(cameraOption + 1) 使索引加1，% cameraUpdatersLength 确保索引在有效范围内循环

        // Call update camera function by its pointer
        // 通过函数指针调用选定的摄像机更新函数
        // cameraUpdaters[cameraOption] 获取当前选定模式对应的函数指针
        // 然后像调用普通函数一样调用它，并传入必要的参数
        cameraUpdaters[cameraOption](&camera, &player, envItems, envItemsLength, deltaTime, screenWidth, screenHeight);
        //----------------------------------------------------------------------------------

        // Draw
        // 绘制阶段
        //----------------------------------------------------------------------------------
        // BeginDrawing() 是 raylib 的函数，开始一帧的绘制
        BeginDrawing();

            // ClearBackground() 是 raylib 的函数，用指定的颜色清空背景
            ClearBackground(LIGHTGRAY); // LIGHTGRAY 是 raylib 预定义的颜色

            // BeginMode2D(camera) 是 raylib 的函数，开始 2D 模式绘制，并应用摄像机变换
            // 之后的所有绘制操作都会受到这个摄像机的影响 (位置、旋转、缩放)
            BeginMode2D(camera);

                // 循环绘制所有环境物体
                for (int i = 0; i < envItemsLength; i++) {
                    // DrawRectangleRec() 是 raylib 的函数，用于绘制一个填充矩形
                    // 参数：矩形对象 (Rectangle)，颜色 (Color)
                    DrawRectangleRec(envItems[i].rect, envItems[i].color);
                }

                // 定义玩家的矩形区域用于绘制
                // 玩家的位置 player.position 是其中心点或脚底点，这里根据需要调整为矩形的左上角
                Rectangle playerRect = { player.position.x - 20, player.position.y - 40, 40.0f, 40.0f };
                DrawRectangleRec(playerRect, RED); // 用红色绘制玩家矩形
                
                // 在玩家精确位置绘制一个小圆点，方便观察玩家的实际坐标点
                DrawCircleV(player.position, 5.0f, GOLD); // GOLD 是 raylib 预定义的颜色

            // EndMode2D() 是 raylib 的函数，结束 2D 模式绘制，恢复默认坐标系
            EndMode2D();

            // 以下绘制的文本不受摄像机影响，直接绘制在屏幕固定位置 (UI元素)
            DrawText("Controls:", 20, 20, 10, BLACK); // DrawText 绘制文本
            DrawText("- Right/Left to move", 40, 40, 10, DARKGRAY);
            DrawText("- Space to jump", 40, 60, 10, DARKGRAY);
            DrawText("- Mouse Wheel to Zoom in-out, R to reset zoom", 40, 80, 10, DARKGRAY);
            DrawText("- C to change camera mode", 40, 100, 10, DARKGRAY);
            DrawText("Current camera mode:", 20, 120, 10, BLACK);
            DrawText(cameraDescriptions[cameraOption], 40, 140, 10, DARKGRAY); // 显示当前摄像机模式的描述

        // EndDrawing() 是 raylib 的函数，结束一帧的绘制，并将后台缓冲区的内容显示到屏幕上
        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    // 反初始化/清理
    //--------------------------------------------------------------------------------------
    CloseWindow();        // CloseWindow() 是 raylib 的函数，关闭窗口并释放 OpenGL 上下文
    //--------------------------------------------------------------------------------------

    return 0; // main 函数返回 0 表示程序正常结束
}

// 更新玩家状态的函数定义
// Player *player: 指向要更新的玩家对象的指针
// EnvItem *envItems: 指向环境物体数组首元素的指针
// int envItemsLength: 环境物体数组的长度
// float delta: 上一帧所花费的时间
void UpdatePlayer(Player *player, EnvItem *envItems, int envItemsLength, float delta)
{
    // IsKeyDown(KEY_LEFT) 是 raylib 的函数，检测左方向键是否被持续按下
    if (IsKeyDown(KEY_LEFT)) player->position.x -= PLAYER_HOR_SPD*delta;
    // player->position.x 访问指针 player 指向的 Player 对象的 position 成员的 x 分量
    // -= PLAYER_HOR_SPD*delta 表示向左移动，移动距离与水平速度和时间间隔成正比

    if (IsKeyDown(KEY_RIGHT)) player->position.x += PLAYER_HOR_SPD*delta; // 向右移动

    // IsKeyDown(KEY_SPACE) 检测空格键是否被持续按下
    // player->canJump 检查玩家是否可以跳跃
    if (IsKeyDown(KEY_SPACE) && player->canJump)
    {
        player->speed = -PLAYER_JUMP_SPD; // 设置向上的初速度 (负值表示向上)
        player->canJump = false;          // 跳跃后暂时不能再跳
    }

    bool hitObstacle = false; // 标记是否碰到障碍物
    // 遍历所有环境物体，进行碰撞检测
    for (int i = 0; i < envItemsLength; i++)
    {
        EnvItem *ei = envItems + i; // ei 指向当前遍历到的环境物体
        // envItems 是数组首地址，envItems + i 通过指针算术运算得到第 i 个元素的地址
        Vector2 *p = &(player->position); // p 指向玩家的位置向量

        // 检查当前环境物体 ei 是否是阻挡类型 (ei->blocking == 1)
        // 并且玩家的 x 坐标在物体的 x 范围内
        // 并且玩家的 y 坐标在当前帧的移动路径上会与物体的 y 坐标发生碰撞
        if (ei->blocking &&
            ei->rect.x <= p->x &&
            ei->rect.x + ei->rect.width >= p->x &&
            ei->rect.y >= p->y && // 玩家当前y坐标在物体上方或与之齐平
            ei->rect.y <= p->y + player->speed*delta) // 玩家下一帧的y坐标会穿过或到达物体表面
        {
            hitObstacle = true;         // 标记碰到障碍物
            player->speed = 0.0f;       // 速度清零 (停止下落)
            p->y = ei->rect.y;          // 将玩家的 y 坐标修正到物体表面 (防止穿透)
            break; // 碰到一个阻挡物就停止检查，因为通常平台游戏一次只处理一个主要碰撞
        }
    }

    // 如果没有碰到障碍物 (在空中)
    if (!hitObstacle)
    {
        player->position.y += player->speed*delta; // 根据当前速度更新 y 坐标 (模拟重力下落或跳跃上升)
        player->speed += G*delta;                  // 根据重力加速度 G 更新速度 (模拟重力影响)
        player->canJump = false;                   // 在空中时不能跳跃
    }
    else player->canJump = true; // 如果碰到了障碍物 (通常是地面或平台)，则可以跳跃
}

// 更新摄像机中心：简单地将摄像机目标设置为玩家位置
void UpdateCameraCenter(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height)
{
    // 设置摄像机偏移，使目标位于屏幕中心
    camera->offset = (Vector2){ width/2.0f, height/2.0f };
    // 设置摄像机目标为玩家当前位置
    camera->target = player->position;
}

// 更新摄像机中心，并限制在地图边界内
void UpdateCameraCenterInsideMap(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height)
{
    camera->target = player->position; // 摄像机目标仍然是玩家
    camera->offset = (Vector2){ width/2.0f, height/2.0f }; // 初始偏移使玩家居中

    // 计算地图的边界 (最小和最大的 x, y 坐标)
    float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;

    for (int i = 0; i < envItemsLength; i++)
    {
        EnvItem *ei = envItems + i;
        minX = fminf(ei->rect.x, minX); // fminf 返回两个 float 数中的较小者
        maxX = fmaxf(ei->rect.x + ei->rect.width, maxX); // fmaxf 返回两个 float 数中的较大者
        minY = fminf(ei->rect.y, minY);
        maxY = fmaxf(ei->rect.y + ei->rect.height, maxY);
    }

    // GetWorldToScreen2D 将世界坐标转换为屏幕坐标
    Vector2 max = GetWorldToScreen2D((Vector2){ maxX, maxY }, *camera); // 地图右下角在屏幕上的坐标
    Vector2 min = GetWorldToScreen2D((Vector2){ minX, minY }, *camera); // 地图左上角在屏幕上的坐标

    // 如果地图的右边界在屏幕内，调整摄像机偏移，使地图右边界与屏幕右边界对齐
    if (max.x < width) camera->offset.x = width - (max.x - width/2.0f);
    // 如果地图的下边界在屏幕内，调整摄像机偏移，使地图下边界与屏幕下边界对齐
    if (max.y < height) camera->offset.y = height - (max.y - height/2.0f);
    // 如果地图的左边界在屏幕内，调整摄像机偏移，使地图左边界与屏幕左边界对齐
    if (min.x > 0) camera->offset.x = width/2.0f - min.x;
    // 如果地图的上边界在屏幕内，调整摄像机偏移，使地图上边界与屏幕上边界对齐
    if (min.y > 0) camera->offset.y = height/2.0f - min.y;
}

// 更新摄像机中心，实现平滑跟随效果
void UpdateCameraCenterSmoothFollow(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height)
{
    // static 变量在函数调用之间保持其值
    static float minSpeed = 30;         // 摄像机最小移动速度
    static float minEffectLength = 10;  // 摄像机开始移动的最小距离阈值
    static float fractionSpeed = 0.8f;  // 摄像机移动速度与距离的比例因子

    camera->offset = (Vector2){ width/2.0f, height/2.0f }; // 保持玩家在屏幕中心
    // Vector2Subtract 计算两个向量的差 (player->position - camera->target)
    Vector2 diff = Vector2Subtract(player->position, camera->target);
    // Vector2Length 计算向量的长度 (模)
    float length = Vector2Length(diff);

    // 如果玩家与摄像机目标的距离大于最小阈值
    if (length > minEffectLength)
    {
        // 计算摄像机移动速度，取 (距离 * 比例因子) 和最小速度中的较大值
        float speed = fmaxf(fractionSpeed*length, minSpeed);
        // Vector2Add 计算两个向量的和
        // Vector2Scale 将向量乘以一个标量 (缩放)
        // camera->target 朝着 player->position 方向移动，移动距离与 speed 和 delta 成正比
        camera->target = Vector2Add(camera->target, Vector2Scale(diff, speed*delta/length));
    }
}

// 更新摄像机，水平方向跟随玩家，垂直方向在玩家落地后平滑调整
void UpdateCameraEvenOutOnLanding(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height)
{
    static float evenOutSpeed = 700; // 垂直平滑调整的速度
    static int eveningOut = false;   // 标记是否正在进行垂直平滑调整
    static float evenOutTarget;      // 垂直平滑调整的目标 y 坐标

    camera->offset = (Vector2){ width/2.0f, height/2.0f }; // 保持玩家在屏幕中心
    camera->target.x = player->position.x; // 摄像机 x 坐标直接跟随玩家 x 坐标

    // 如果正在进行垂直平滑调整
    if (eveningOut)
    {
        // 如果目标 y 坐标大于当前摄像机 y 坐标 (需要向上移动)
        if (evenOutTarget > camera->target.y)
        {
            camera->target.y += evenOutSpeed*delta; // 向上移动

            // 如果超过了目标，则设置为目标值，并结束平滑调整
            if (camera->target.y > evenOutTarget)
            {
                camera->target.y = evenOutTarget;
                eveningOut = false;
            }
        }
        else // 目标 y 坐标小于或等于当前摄像机 y 坐标 (需要向下移动或已到达)
        {
            camera->target.y -= evenOutSpeed*delta; // 向下移动

            // 如果超过了目标，则设置为目标值，并结束平滑调整
            if (camera->target.y < evenOutTarget)
            {
                camera->target.y = evenOutTarget;
                eveningOut = false;
            }
        }
    }
    else // 如果没有在进行垂直平滑调整
    {
        // 如果玩家可以跳跃 (在地面上)，且垂直速度为0 (静止在地面上)，
        // 并且玩家的 y 坐标与摄像机目标的 y 坐标不同
        if (player->canJump && (player->speed == 0) && (player->position.y != camera->target.y))
        {
            eveningOut = true; // 开始垂直平滑调整
            evenOutTarget = player->position.y; // 设置调整目标为玩家当前的 y 坐标
        }
    }
}

// 更新摄像机，当玩家靠近屏幕边缘时，通过移动摄像机来“推”开玩家
void UpdateCameraPlayerBoundsPush(Camera2D *camera, Player *player, EnvItem *envItems, int envItemsLength, float delta, int width, int height)
{
    // bbox (bounding box) 定义了一个在屏幕中心的矩形区域，玩家应尽量保持在该区域内
    // 这里的 {0.2f, 0.2f} 表示这个区域占据屏幕宽高的 20%
    static Vector2 bbox = { 0.2f, 0.2f };

    // GetScreenToWorld2D 将屏幕坐标转换为世界坐标
    // 计算 bbox 在世界坐标系中的最小 (左上) 和最大 (右下) 坐标
    Vector2 bboxWorldMin = GetScreenToWorld2D((Vector2){ (1 - bbox.x)*0.5f*width, (1 - bbox.y)*0.5f*height }, *camera);
    Vector2 bboxWorldMax = GetScreenToWorld2D((Vector2){ (1 + bbox.x)*0.5f*width, (1 + bbox.y)*0.5f*height }, *camera);
    
    // 设置摄像机偏移，使得 bbox 的左上角对应屏幕的 ( (1-bbox.x)*0.5*width, (1-bbox.y)*0.5*height )
    camera->offset = (Vector2){ (1 - bbox.x)*0.5f * width, (1 - bbox.y)*0.5f*height };

    // 如果玩家位置超出了 bbox 的边界，则移动摄像机目标
    // 这样做的效果是，当玩家试图移出 bbox 时，摄像机反向移动，使得玩家看起来被推回 bbox 内
    if (player->position.x < bboxWorldMin.x) camera->target.x = player->position.x;
    if (player->position.y < bboxWorldMin.y) camera->target.y = player->position.y;
    if (player->position.x > bboxWorldMax.x) camera->target.x = bboxWorldMin.x + (player->position.x - bboxWorldMax.x);
    if (player->position.y > bboxWorldMax.y) camera->target.y = bboxWorldMin.y + (player->position.y - bboxWorldMax.y);
}
